package cytargetlinker.conversion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.bridgedb.IDMapperStack;

import cytargetlinker.conversion.ArgsParser.AFilesAttributes;
import cytargetlinker.conversion.ArgsParser.AFilesIn;
import cytargetlinker.conversion.ArgsParser.AFilesOut;
import cytargetlinker.conversion.ArgsParser.AHelp;
import cytargetlinker.conversion.ArgsParser.GraphBuilder;
import cytargetlinker.conversion.graph.Graph;
import cytargetlinker.conversion.graph.Graph.Edge;


/**
 * Converts miRNA target text files from MiRecords to a XGMML or GML network.
 * 
 * The input files for this script can be downloaded here:
 * 
 * @author Thomas, Pooja, Naime, Tina
 */
public class Microcosm {
	
	private final static Logger log = Logger.getLogger(MiRecords.class.getName());
	private static Args pargs;
	private interface Args extends AHelp, AFilesIn, AFilesOut, AFilesAttributes {}
	
	/**
	 * MAIN METHOD
	 * USAGE: java -cp conversion.jar cytargetlinker.conversion.Microcosm 
	 * ARGUMENTS: 
	 * -i = input file
	 * -o = output file
	 * --bridgeDbSource = BridgeDb mapping file for the source nodes (gene database)
	 * --bridgeDbTarget = BridgeDb mapping file for the target nodes (miRBase database)
	 * -a = Annotation file (csv containing Transcript/Gene mapping - separated with a comma)
	 * --organism "mouse" or "human" 
	 */
	public static void main(String argv[]) throws Exception {
		pargs = ArgsParser.parse(argv, Args.class);
		if(pargs.getAnnotationFile() != null) {
			Microcosm converter = new Microcosm();
			converter.startConversion();
		} else {
			log.severe("Please add annotation file to map from Ensembl transcript to Ensembl gene id.");
		}
	}
	
	private IDMapperStack gdb;
	private Graph graph;
	private Map<String, Integer> index;
	private Map<String, List<String>> edges;
	
	private List<String> genesNotFound;
	private List<String> foundConnections;
	private Map<String, String> annotationMap;
	
	private int countEdge = 0;
	private boolean mapping = true;
	
	public Microcosm() throws Exception {
		edges = new HashMap<String, List<String>>();
		index = new HashMap<String, Integer>();
		annotationMap = new HashMap<String, String>();
		foundConnections = new ArrayList<String>();
		genesNotFound = new ArrayList<String>();
	}
	
	private void startConversion() throws Exception {
		File in = pargs.getInput().get(0);
		if(in != null) {
			Utils.setUpLogger(log, in, false);
			gdb = Utils.initIDMapper(pargs.getBridgeDbFileSource(), pargs.getBridgeDbFileTarget());
			if(gdb == null) {
				mapping = false;
				log.info("no identifier mapping");
			}
			log.info("conversion of microcosm file started ...\n");

			ArgsParser.convertAndWrite(pargs, pargs, new GraphBuilder() {
				public Graph buildGraph(File in) throws Exception {
					return importMicrocosm(in);
				}
			});
				
			log.info("MicroCosm network file created.\n\n");
		} else {
			System.out.println("Please specify input file!");
		}
	}
	
	protected Graph importMicrocosm(File in) throws IOException {
		graph = new Graph();
		
		readAnnotations();
		setNetworkAttributes(in);
		parseMicrocosm();
		
		
		log.info(foundConnections.size() + " interactions have been found.\n" + genesNotFound.size() + " transcripts were not mapped to genes.\n");
		cleanUp();
		return graph;
	}
	
	private void cleanUp() {
		edges.clear();
		index.clear();
		annotationMap.clear();
		foundConnections.clear();
		genesNotFound.clear();
	}
	
	private List<String> ids;
	
	private void parseMicrocosm() {
		ids = new ArrayList<String>();
		for(File f : pargs.getInput()) {
			try {
				BufferedReader br = new BufferedReader(new FileReader(f));
				String[] header = readHeader(br);
			
				if(header != null) {				
					String line = null;
					
					List<String[]> rows = new ArrayList<String[]>();
					while((line = br.readLine()) != null) {
						if(!line.startsWith("#") && !line.equals("")) {
							rows.add(line.split("\t", header.length));
						} 
					}
					
					for(String[] row : rows) {
						String transcriptId = row[index.get("TRANSCRIPT_ID")];
						String geneName = row[index.get("EXTERNAL_NAME")];
						String mirna = row[index.get("SEQ")];
						String score = row[index.get("SCORE")];
						String pValue = row[index.get("PVALUE_OG")];
						
						if(annotationMap.containsKey(transcriptId)) {
							if(!ids.contains(annotationMap.get(transcriptId))) {
								createGeneNode(annotationMap.get(transcriptId), geneName);
								ids.add(annotationMap.get(transcriptId));
							}
							if(!ids.contains(mirna)) {
								createMiRNANode(mirna);
								ids.add(mirna);
							}
							addEdge(annotationMap.get(transcriptId), mirna, score, pValue);
						} else {
							if(!genesNotFound.contains(transcriptId)) genesNotFound.add(transcriptId);
						}
					}
				}	
			} catch (IOException e) {
				log.warning("Could not read input file " + f.getAbsolutePath());
			}
		}
	}
	
	private void addEdge(String gene, String mirna, String score, String pValue) {
		if(edges.containsKey(gene)) {
			if(!edges.get(gene).contains(mirna)) {
				Edge e = graph.addEdge("" + countEdge, graph.getNode(mirna),graph.getNode(gene));
				try {
					Double d = Double.parseDouble(score);
					e.setAttribute("score", d.toString());
					Double pvalue = Double.parseDouble(pValue);
					e.setAttribute("pvalue", pvalue.toString());
				} catch(NumberFormatException ex) {
					e.setAttribute("score", "");
					e.setAttribute("pvalue", "");
				}
				
				edges.get(gene).add(mirna);
				foundConnections.add(gene + "\t" + mirna);
				countEdge++;
			}
		} else {
			Edge e = graph.addEdge("" + countEdge, graph.getNode(mirna),graph.getNode(gene));
			try {
				Double d = Double.parseDouble(score);
				e.setAttribute("score", d.toString());
				Double pvalue = Double.parseDouble(pValue);
				e.setAttribute("pvalue", pvalue.toString());
			} catch(NumberFormatException ex) {
				e.setAttribute("score", "");
				e.setAttribute("pvalue", "");
			}
			List<String> list = new ArrayList<String>();
			list.add(mirna);
			foundConnections.add(gene + "\t" + mirna);
			edges.put(gene, list);
			countEdge++;
		}
	}
	
	private String[] readHeader(BufferedReader br) throws IOException {
		String[] header = null;
		String line = null;
		while((line = br.readLine()) != null) {
			if(line.startsWith("##GROUP")) {
				header = line.split("\t");
				index = new HashMap<String, Integer>();
				
				for(int i = 0; i < header.length; i++) index.put(header[i], i);
				break;
			}
		}
		return header;
	}
	
	private void setNetworkAttributes(File input) {
		graph.setTitle("Microcosm " + pargs.getOrganism());
		graph.setAttribute(CommonAttributes.DATABASE.getName(), "Microcosm " + pargs.getOrganism());
	
		graph.setAttribute(CommonAttributes.TYPE.getName(), "predicted miRNA targets");
		graph.setAttribute(CommonAttributes.SOURCE_FILE.getName(), input.getName());
		graph.setAttribute(CommonAttributes.IDENTIFIERS.getName(), "identifiers");
		graph.setAttribute(CommonAttributes.TARGET_DATASOURCE.getName(), "Ensembl");
		graph.setAttribute(CommonAttributes.SOURCE_DATASOURCE.getName(), "miRBase");
		graph.setAttribute(CommonAttributes.SOURCE_TYPE.getName(), "mirna");
		graph.setAttribute(CommonAttributes.TARGET_TYPE.getName(), "gene");
	}

	private void readAnnotations() {
		File file = new File(pargs.getAnnotationFile());
		if(file.exists()) {
			try {
				BufferedReader reader = new BufferedReader(new FileReader(file));
				String line;
				while((line = reader.readLine()) != null) {
					String [] buffer = line.split(",");
					if(!annotationMap.containsKey(buffer[1])) {
						annotationMap.put(buffer[1], buffer[0]);
					}
				}
				reader.close();
			} catch (IOException e) {
				log.severe("Could not read annotation file.\n");
				System.exit(0);
			}
		} else {
			log.severe("Annotation file does not exist.\n");
			System.exit(0);
		}
	}

	private void createGeneNode(String geneId, String geneName) {
		Map<String, String> attr = new HashMap<String, String>();
		attr.put("label", geneName);
		attr.put("name", geneName);
		attr.put("organism", pargs.getOrganism());
		attr.put("Emsebml", geneId);
		
		if(!mapping) {
			// user didn't define BridgeDb mapping file(s)
			Utils.createNode(graph, geneId, attr);
		} else {
			// user did define BridgeDb mapping file(s)
			String sysIn = "";
			List<String> sysCodeOut = new ArrayList<String>();
			if(pargs.getOrganism().equals("mouse")) {
				sysCodeOut.add("EnMm");
				sysIn = "EnMm";
			} else if(pargs.getOrganism().equals("human")) {
				sysCodeOut.add("EnHs");
				sysIn = "EnHs";
			}
			sysCodeOut.add("H");
			sysCodeOut.add("L");
			
			Utils.createNode(graph, geneId, sysIn, sysCodeOut, attr, gdb);
		}
	}

	private void createMiRNANode(String mirnaName) {
		Map<String, String> attr = new HashMap<String, String>();
		attr.put("label", mirnaName);
		attr.put("name", mirnaName);
		attr.put("organism", pargs.getOrganism());
		attr.put("miRNA", mirnaName);
		if(!mapping) {
			// user didn't define BridgeDb mapping file(s)
			Utils.createNode(graph, mirnaName, attr);
		} else {
			// user did define BridgeDb mapping file(s)
			List<String> sysCodeOut = new ArrayList<String>();
			sysCodeOut.add("Mbm");
			sysCodeOut.add("Mb");
			Utils.createNode(graph, mirnaName, "Mb", sysCodeOut, attr, gdb);
		}
	}
    
	
}
